package com.bjy.qa.agent.tester.handler.tester;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.bjy.qa.agent.interceptor.tester.AfterInterceptor;
import com.bjy.qa.agent.interceptor.tester.BeforeInterceptor;
import com.bjy.qa.agent.interceptor.InterceptorConf;
import com.bjy.qa.agent.context.Context;
import com.bjy.qa.agent.context.HandlerContextMap;
import com.bjy.qa.agent.enumtype.ConditionEnum;
import com.bjy.qa.agent.enumtype.ErrorHandlingType;
import com.bjy.qa.agent.enumtype.RunStatus;
import com.bjy.qa.agent.enumtype.RunningModeStatus;
import com.bjy.qa.agent.exception.BreakException;
import com.bjy.qa.agent.exception.MyException;
import com.bjy.qa.agent.model.Api;
import com.bjy.qa.agent.model.HandleDes;
import com.bjy.qa.agent.model.KeyValueStore;
import com.bjy.qa.agent.model.Step;
import com.bjy.qa.agent.response.Response;
import com.bjy.qa.agent.tester.LogUtil;
import com.bjy.qa.agent.tester.RunStepThread;
import com.bjy.qa.agent.tester.handlers.StepHandlers;
import com.bjy.qa.agent.tools.EnumUtil;
import com.bjy.qa.agent.tools.SpringTool;
import com.bjy.qa.agent.tools.json.JsonHelper;
import com.bjy.qa.agent.tools.script.GroovyScript;
import com.bjy.qa.agent.tools.script.JavaScriptScript;
import com.bjy.qa.agent.tools.script.PythonScript;
import com.bjy.qa.agent.tools.script.Script;
import com.bjy.qa.agent.tools.security.TripleDESUtil;
import com.bjy.qa.agent.transport.command.*;
import com.bjy.qa.agent.transport.websocket.EndingType;
import com.bjy.qa.agent.transport.websocket.IWebSocketService;
import com.jcraft.jsch.JSchException;
import com.rslai.commons.ssh.*;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.*;
import java.util.concurrent.TimeoutException;

import static com.bjy.qa.agent.tools.security.TripleDESUtil.decrypt;

/**
 * Tester 步骤执行器
 */
public class TesterStepHandler {
    private static final Logger logger = LoggerFactory.getLogger(TesterStepHandler.class);

    public LogUtil logUtil = new LogUtil(); // 设置上报运行结果工具类
    private Context context; // 将当前套件的 Context
    private int holdTime = 0; // 步骤间隔时间
    private RunStatus status = RunStatus.PASSED; // 测试任务运行状态

    private Response response = new Response(); // 保存返回数据(也可以理解成切面 - 某个步骤需要改变就改，不需要就不改，需要校验就校验)

    private static List<Constructor> beforeInterceptors = new LinkedList<>(); // 前置拦截器 列表
    private static List<Constructor> afterInterceptors = new LinkedList<>(); // 后置拦截器 列表

    static {
        InterceptorConf interceptorConf = SpringTool.getBean(InterceptorConf.class); // 获得 拦截器 配置

        // 遍历加载 tester 的前置拦截器
        for (String className : interceptorConf.getTesterBeforeInterceptors()) {
            try {
                if (StringUtils.isNotBlank(className)) {
                    Class clazz = Class.forName(className);
                    Constructor constructor = clazz.getConstructor(Map.class, Step.class); // 获取构造器对象
                    beforeInterceptors.add(constructor);
                    logger.info("加载 tester 的前置拦截器: {}", className);
                }
            } catch (Throwable e) {
                e.printStackTrace();
                logger.error("加载 tester 的前置拦截器错误，className：" + className + "\t" + e.getMessage() );
                throw new MyException("加载 tester 的前置拦截器错误，className：" + className + "\n" + e.getMessage());
            }
        }

        // 遍历加载 tester 的后置拦截器
        for (String className : interceptorConf.getTesterAfterInterceptors()) {
            try {
                if (StringUtils.isNotBlank(className)) {
                    Class clazz = Class.forName(className);
                    Constructor constructor = clazz.getConstructor(Map.class, Step.class, Response.class, Context.class); // 获取构造器对象
                    afterInterceptors.add(constructor);
                    logger.info("加载 tester 的后置拦截器: {}", className);
                }
            } catch (Throwable e) {
                e.printStackTrace();
                logger.error("加载 tester 的后置拦截器错误，className：" + className + "\t" + e.getMessage() );
                throw new MyException("加载 tester 的后置拦截器错误，className：" + className + "\n" + e.getMessage());
            }
        }
    }

    /**
     * 构造函数
     * @param catalogType 分类类型 - 上报日志类型
     * @param caseId 测试用例 ID
     * @param iterationCount 迭代次数
     * @param resultId 测试任务结果 ID
     * @param type 运行类型（DEBUGGING、TESTING）
     * @param sessionId 当 type=DEBUGGING 需要传入sessionId，服务器收到上报后通过这个 sessionId 发送到对应的 websocket
     */
    public TesterStepHandler(int catalogType, int caseId, String iterationCount, int resultId, RunningModeStatus type, String sessionId) {
        logUtil.resultId = resultId;
        logUtil.catalogType = catalogType;
        logUtil.caseId = caseId;
        logUtil.iterationCount = iterationCount;
        logUtil.type = type;
        logUtil.sessionId = sessionId;

        // 根据 resultId 得到一个 Context，要是没有则创建一个新的 Context
        context = HandlerContextMap.getContext(resultId);
        if (context == null) {
            context = new Context();
            HandlerContextMap.addContext(resultId, context);
        }
    }

    /**
     * 关闭 driver
     */
    public void closeDriver() {
        // 遍历关闭 WebSocket
        for (Map.Entry<String, IWebSocketService> entry : this.context.getWebSocketServiceMap().entrySet()) {
            String key = entry.getKey();
            IWebSocketService iWebSocketService = entry.getValue();

            iWebSocketService.close("关闭 WebSocket: " + key); // 关闭 WebSocket
            iWebSocketService = null; // 置空
            this.context.getWebSocketServiceMap().remove(key); // 从 WebSocketServiceMap 中移除
        }
    }

    /**
     * 设置全局参数
     * @param jsonObject 全局参数
     */
    public void setGlobalParams(JSONObject jsonObject) {
        if (jsonObject == null) {
            return;
        }
        for (Map.Entry<String, Object> entry : jsonObject.entrySet()) {
            if (entry.getValue().toString().indexOf(TripleDESUtil.CIPHERTEXT_PREFIX) == 0) {
                this.context.addGlobalParas(entry.getKey(), decrypt(entry.getValue().toString())); // 用户参数需要解密
            } else {
                this.context.addGlobalParas(entry.getKey(), entry.getValue()); // 普通参数
            }
        }
    }

    public LogUtil getLog() {
        return logUtil;
    }

    /**
     * 执行步骤
     * @param stepJSON 步骤 JSON
     * @param handleDes 步骤描述
     * @throws Throwable
     */
    public void runStep(JSONObject stepJSON, HandleDes handleDes) throws Throwable {
        Step step = stepJSON.getObject("step", Step.class); // 得到步骤

        Thread.sleep(holdTime * 1000); // 步骤间隔

        before(handleDes, this.context.getGlobalParas(), step);

        handleDes.setStepDesc(step.getDesc());
        switch (step.getStepType()) {
            case METHOD_STEP_HOLD:
                stepHold(handleDes, Integer.parseInt(step.getExtra1()));
                break;
            case METHOD_WAIT:
                wait(handleDes, Integer.parseInt(step.getExtra1()));
                break;
            case METHOD_WEB_SOCKET_WAIT:
                webSocketWait(handleDes, Integer.parseInt(step.getExtra1()));
                break;
            case PUBLIC_STEP:
                publicStep(handleDes, step.getExtra1(), stepJSON.getJSONObject("step").getJSONArray("pubSteps"));
                return;
            case INTERFACE_SSH:
                ssh(handleDes, step.getApi(), step.getExtra1(), step.getExtra2());
                break;
            case METHOD_ASSERT:
                asserts(handleDes, step.getExtra1());
                break;
            case INTERFACE_HTTP:
                http(handleDes, step.getApi(), step.getExtra1(), step.getExtra2(), step.getExtra3(), step.getExtra4(), step.getExtra5());
                break;
            case INTERFACE_WEB_SOCKET:
                webSocket(handleDes, step.getApi(), step.getExtra4(), step.getExtra2(), step.getExtra3(), step.getExtra5(), step.getExtra1());
                break;
            case INTERFACE_DATABASE:
                dataBase(handleDes, step.getApi(), step.getExtra3());
                break;
            case METHOD_SAVE_PARAS:
                saveParas(handleDes, step.getExtra1());
                break;
            case METHOD_SET_PARAS:
                setParas(handleDes, step.getExtra1());
                break;
            case RUN_SCRIPT:
                runScript(handleDes, step.getExtra1(), step.getExtra2());
                break;
            default:
                unknown(handleDes, step);
        }

        after(handleDes, this.context.getGlobalParas(), step, this.response, this.context);

        afterHandle(step, handleDes);
    }

    /**
     * 前置处理
     * @param globalParas
     * @param step
     */
    private void before(HandleDes handleDes, Map<String, Object> globalParas, Step step) {
        // 遍历执行 tester 的前置拦截器
        for (Constructor constructor : beforeInterceptors) {
            try {
                BeforeInterceptor beforeInterceptor = (BeforeInterceptor) constructor.newInstance(globalParas, step); // 利用构造器创建一个对象
                beforeInterceptor.execute();
            } catch (Exception e) {
                e.printStackTrace();
                throw new BreakException("运行 tester 的前置拦截器错误，className：" + constructor.getName() + "\t" + e.getMessage());
            }
        }
    }

    /**
     * 后置处理
     * @param handleDes
     * @param globalParas
     * @param step
     * @param response
     */
    private void after(HandleDes handleDes, Map<String, Object> globalParas, Step step, Response response, Context context) {
        // 遍历执行 tester 的后置拦截器
        for (Constructor constructor : afterInterceptors) {
            try {
                AfterInterceptor afterInterceptor = (AfterInterceptor) constructor.newInstance(globalParas, step, response, context); // 利用构造器创建一个对象
                afterInterceptor.execute();
            } catch (Exception e) {
                e.printStackTrace();
                throw new BreakException("运行 tester 的后置拦截器错误，className：" + constructor.getName() + "\t" + e.getMessage());
            }
        }
    }

    /**
     * 后置处理
     * @param step 步骤
     * @param handleDes handleDes
     * @throws Throwable
     */
    public void afterHandle(Step step, HandleDes handleDes) throws Throwable {
        ErrorHandlingType error = step.getErrorHandlingType();
        String stepDesc = handleDes.getStepDesc();
        String stepType = handleDes.getStepType();
        String detail = handleDes.getDetail();
        Throwable e = handleDes.getE();
        if (e == null) {
            logUtil.sendStepLog(RunStatus.PASSED, stepDesc, stepType, detail);
        } else {
            switch (error) {
                case IGNORE:
                    if (step.getConditionType().equals(ConditionEnum.NONE)) {
                        logUtil.sendStepLog(RunStatus.PASSED, stepDesc, stepType + " 异常！已忽略...", detail);
                    } else {
                        ConditionEnum conditionType = step.getConditionType();
                        String type = "「%s」步骤「%s」异常".formatted(conditionType.getName(), stepType);
                        logUtil.sendStepLog(RunStatus.ERROR, stepDesc, type, detail);
                        exceptionLog(e);
                    }
                    break;
                case WARNING:
                    logUtil.sendStepLog(RunStatus.WARN, stepDesc, stepType + " 异常！", detail);
                    setResultDetailStatus(RunStatus.WARN);
                    exceptionLog(e);
                    break;
                case STOP:
                    logUtil.sendStepLog(RunStatus.ERROR, stepDesc, stepType + " 异常！", detail);
                    setResultDetailStatus(RunStatus.FAILED);
                    exceptionLog(e);
                    throw e;
            }
            // 非条件步骤清除异常对象
            if (step.getConditionType().equals(ConditionEnum.NONE)) {
                handleDes.clear();
            }
        }
    }

    /**
     * 异常信息上报
     * @param e
     */
    public void exceptionLog(Throwable e) {
        logUtil.sendStepLog(RunStatus.WARN, "", "", "异常信息： " + e.fillInStackTrace().toString());
    }

    /**
     * 发送测试执行状态
     */
    public void sendStatus() {
        logUtil.sendStatusLog(status);
    }

    /**
     * 设置测试执行状态
     * @param status 测试执行状态
     */
    public void setResultDetailStatus(RunStatus status) {
        if (status.getValue() > this.status.getValue()) {
            this.status = status;
        }
    }

    /**
     * 未实现的 step
     * @param handleDes handleDes
     * @param step step 信息
     */
    public void unknown(HandleDes handleDes, Step step) {
        handleDes.setStepType("未实现的 step");
        handleDes.setE(new MyException(step.toString()));

        logger.error("未实现的 step: {}", step);
    }

    public void stepHold(HandleDes handleDes, int time) {
        handleDes.setStepType("设置全局步骤间隔");
        handleDes.setDetail(time + "s");
        holdTime = time;
    }

    public void wait(HandleDes handleDes, int time) {
        handleDes.setStepType("等待");
        try {
            Thread.sleep(time * 1000);
            handleDes.setDetail(time + "s");
        } catch (InterruptedException e) {
            handleDes.setE(e);
        }
    }

    public void webSocketWait(HandleDes handleDes, int time) {
        handleDes.setStepType("WebSocket 智能等待");
        handleDes.setDetail(time + " 秒");
        try {
            long timeOut = System.currentTimeMillis() + time * 1000;
            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                }

                // 遍历所有的 WebSocketService，判断是否有 pending 的内容，没有则跳出循环
                boolean hasPending = false;
                for (Map.Entry<String, IWebSocketService> entry : this.context.getWebSocketServiceMap().entrySet()) {
                    if (entry.getValue().hasPending()) {
                        hasPending = true;
                        break;
                    }
                }
                if (!hasPending) {
                    break;
                }

                // 超过智能等待时间，抛出异常
                if (System.currentTimeMillis() >= timeOut) {
                    // 遍历所有的 WebSocketService，收集所有错误信息
                    StringBuffer sb = new StringBuffer();
                    for (Map.Entry<String, IWebSocketService> entry : this.context.getWebSocketServiceMap().entrySet()) {
                        String str = entry.getValue().getPendingMessages();
                        if (str.length() != 0) {
                            sb.append("通道: " + entry.getKey() + "\n");
                            sb.append(str);
                            sb.append("\n");
                        }
                    }
                    if (sb.length() > 0) {
                        throw new MyException(sb.toString());
                    }
                }
            }
        } catch (Exception e) {
            handleDes.setE(e);
        }
    }

    // TODO: 2022/12/19 临时修复公共步骤设置了失败中断，但失败后不中断的 bug
    // public void publicStep(HandleDes handleDes, String name, JSONArray stepArray) {
    public void publicStep(HandleDes handleDes, String name, JSONArray stepArray) throws Throwable {
        handleDes.setStepType("执行公共步骤 " + name);
        handleDes.setDetail("");
        logUtil.sendStepLog(RunStatus.WARN, handleDes.getStepDesc(), "公共步骤「" + name + "」开始执行", "");
        for (Object publicStep : stepArray) {
            JSONObject stepDetail = (JSONObject) publicStep;
            try {
                SpringTool.getBean(StepHandlers.class)
                        .runStep(stepDetail, handleDes, (RunStepThread) Thread.currentThread());
            } catch (Throwable e) {
                handleDes.setE(e);
                // TODO: 2022/12/19 临时修复公共步骤设置了失败中断，但失败后不中断的 bug
                // break;
                throw e;
            }
        }
        logUtil.sendStepLog(RunStatus.WARN, handleDes.getStepDesc(), "公共步骤「" + name + "」执行完毕", "");
    }

    /**
     * 执行 ssh 命令
     * @param handleDes handleDes
     * @param api api
     * @param sshConf ssh 配置(端口、是否用户名密码登录、是否跳板机、跳板机要登录的服务器）
     * @param cmds 要执行的命令
     */
    private void ssh(HandleDes handleDes, Api api, String sshConf, String cmds) {
        handleDes.setStepType("SSH 命令");
        try {
            if (api == null) {
                throw new Exception("api 不能为空");
            }

            // 读取、设置 ssh 配置
            StringBuffer desDetail = new StringBuffer();
            JSONObject conf = JSON.parseObject(sshConf);
            SSHConfig sshConfig = new SSHConfig();
            sshConfig.setHost(api.getUrl()); // ssh 服务器地址
            desDetail.append(" host:「" + sshConfig.getHost() + "」");
            sshConfig.setPort(conf.getInteger("port"));
            desDetail.append(" port:「" + sshConfig.getPort() + "」");
            sshConfig.setJumpServer(conf.getBoolean("isJumpServer"));
            desDetail.append(" isJumpServer:「" + sshConfig.isJumpServer() + "」");
            if (conf.getBoolean("isPasswordAuth")) {
                sshConfig.setUserAuthType(UserAuthTypeEnum.PASSWORD);
                sshConfig.setUsername(conf.getString("userName")); // 登录 ssh 用户名
                sshConfig.setPassword(conf.getString("password")); // 登录 ssh 密码
                desDetail.append(" isPasswordAuth:「true」");
                desDetail.append(" userName:「" + sshConfig.getUsername() + "」");
                desDetail.append(" password:「******」");
            } else {
                sshConfig.setUserAuthType(UserAuthTypeEnum.PUBKEY);
                desDetail.append(" isPasswordAuth:「false」");
            }
            if (StringUtils.isNotEmpty(conf.getString("serverIp"))) {
                cmds = conf.getString("serverIp") + " \n " + cmds;
            }
            desDetail.append(" cmds:「" + cmds + "」");
            handleDes.setDetail(desDetail.toString());

            // 执行 ssh 命令
            List<Map> sshResponseList = new LinkedList<>();
            if (StringUtils.isNotEmpty(cmds)) {
                SSH ssh = null;
                try {
                    ssh = new Jsch(sshConfig);
                    for (String cmd : cmds.replace("\r\n", "\n").split("\n")) {
                        Map<String, Object> result = new HashMap<>();
                        try {
                            SSHResponse sshResponse = ssh.execute(cmd);
                            result.put("status", true);
                            result.put("cmd", sshResponse.getCmd());
                            result.put("body", sshResponse.getBody());
                            result.put("prompt", sshResponse.getPrompt());
                        } catch (Exception e) {
                            result.put("status", false);
                            result.put("cmd", cmd);
                            result.put("body", e.getMessage());
                            result.put("prompt", "");
                        }
                        sshResponseList.add(result);
                    }
                } catch (JSchException e) {
                    throw new Exception(e);
                } catch (IOException e) {
                    throw new Exception(e);
                } catch (TimeoutException e) {
                    throw new Exception(e);
                } finally {
                    if (ssh != null) {
                        try {
                            ssh.disconnect(); // 释放ssh链接
                        } catch (TimeoutException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }

            // 设置返回值
            Map<String, List> ret = new HashMap<>();
            ret.put("ret", sshResponseList);
            response.setBody(JSON.toJSONString(ret, new SerializerFeature[]{SerializerFeature.WriteMapNullValue}));
            logUtil.sendStepLog(RunStatus.INFO, handleDes.getStepDesc(), "", "SSH 命令结果: " + response.getBody().toString());
        } catch (Exception e) {
            handleDes.setE(e);
        }
    }

    /**
     * 断言
     * @param handleDes handleDes
     * @param expected 期望值
     */
    private void asserts(HandleDes handleDes, String expected) {
        handleDes.setStepType("断言");
        handleDes.setDetail("期望：" + expected);
        try {
            Map<String, String> exp = new HashMap<>();
            exp.put("body", expected);

            response.verify(exp);
        } catch (Throwable e) {
            handleDes.setE(e);
        }
    }

    /**
     * http 请求
     * @param handleDes handleDes
     * @param api api
     * @param heads heads
     * @param urlParams urlParams
     * @param body body
     * @param cookies cookies
     * @param assertStr assert 断言
     */
    private void http(HandleDes handleDes, Api api, String heads, String urlParams, String body, String cookies, String assertStr) {
        /*====================================================================
          = 修改这里的代码时，别忘记修改 DebugTesterStepHandler 中的 http 方法的代码 =
          ==================================================================== */

        handleDes.setStepType("HTTP 请求");
        handleDes.setDetail("" + api.getName() + " url：「" + api.getUrl() + "」" + " method：「" + api.getMethod() + "」" + " heads：「" + heads + "」" + " body：「" + body + "」" + " urlParams：「" + urlParams + "」" + " cookies：「" + cookies + "」" + " assert：「" + assertStr + "」");

        // http 请求
        try {
            String url = api.getUrl();
            String desc = api.getName();
            String method = api.getMethod();
            String mimeType = api.getExtra1(); // body数据类型

            if (!"POST".equalsIgnoreCase(method) && !"GET".equalsIgnoreCase(method) && !"DELETE".equalsIgnoreCase(method) && !"PUT".equalsIgnoreCase(method) && !"binary".equalsIgnoreCase(method)) {
                throw new Exception("不支持的 method 类型 「" + method + "」，目前只支持 POST、GET、PUT、DELETE、BINARY");
            }

            List<KeyValueStore> paramList = new ArrayList<>();

            // 组装 head 参数
            if (StringUtils.isNotEmpty(heads)) {
                paramList.add(new KeyValueStore("http-headers", heads));
            }

            // get 请求 body 为 {} 的时候，会在 header 中添加 Content_Length=2，这时有些服务器会报 411 错。所以当 get 且 body 为 {} 时，body 设置为空
            if ("GET".equalsIgnoreCase(method) && StringUtils.isNotEmpty(body) && "{}".equals(body)) {
                body = "";
            }

            // get 方法且 body 不为空添加 /ENTITY（以便发送带 body 的 get 请求）
            if ("GET".equalsIgnoreCase(method) && StringUtils.isNotEmpty(body)) {
                method = method + "/ENTITY";
            }

            // 组装 urlParams 参数
            if (StringUtils.isNotEmpty(urlParams)) {
                if ("GET".equalsIgnoreCase(method) && StringUtils.isEmpty(body)) { // 如果 get 方法 且 body 为空。不重新生成 url，直接用 paramList 向下层传 urlParams
                    JSON.parseObject(urlParams).forEach((k, v) -> {
                        if (v == null) {
                            paramList.add(new KeyValueStore(k, v));
                        } else {
                            paramList.add(new KeyValueStore(k, v.toString()));
                        }
                    });
                } else { // 否则，根据 urlParams 参数拼接带参数的 url
                    List<KeyValueStore> tmpList = new ArrayList<>();
                    JSON.parseObject(urlParams).forEach((k, v) -> {
                        if (v == null) {
                            tmpList.add(new KeyValueStore(k, v));
                        } else {
                            tmpList.add(new KeyValueStore(k, v.toString()));
                        }
                    });
                    url = this.context.getHttpService().getHttpGetURL(url, tmpList);
                }
            }

            // 组装 body 参数
            if (StringUtils.isNotEmpty(body)) {
                if ("JSON".equalsIgnoreCase(mimeType)) {
                    paramList.add(new KeyValueStore("param", body));
                } else if ("FORM".equalsIgnoreCase(mimeType)) {
                    JSON.parseObject(body).forEach((k, v) -> {
                        if (v instanceof String || v instanceof Map) {
                            paramList.add(new KeyValueStore(k, v));
                        } else {
                            throw new MyException("当 body 为 FORM 表单时，所有参数只能是 String 类型。key: " + k + "， value: " + v);
                        }
                    });
                } else {
                    throw new Exception("不支持的 body 类型 「" + mimeType + "」，目前 body 只支持 FORM、JSON");
                }
            }

            // 发送 http 请求
            ExecuteCommand executeCommand = new HttpExecuteCommand(this.context.getHttpService(), String.valueOf(logUtil.resultId), String.valueOf(logUtil.caseId), String.valueOf(logUtil.iterationCount), url, method, desc);
            this.response = executeCommand.execute(paramList);
            logUtil.sendStepLog(RunStatus.INFO, handleDes.getStepDesc(), "", "HTTP 请求结果: " + response.getBody().toString());

            // 请求后断言
            if (!"{}".equals(assertStr)) {
                try {
                    Map<String, String> exp = new HashMap<>();
                    exp.put("body", assertStr);

                    this.response.verify(exp);
                } catch (Throwable e) {
                    handleDes.setE(e);
                }
            }
        } catch (Exception e) {
            handleDes.setE(e);
        }
    }

    /**
     * webSocket 请求
     * @param handleDes handleDes
     * @param api api
     * @param options 扩展数据（通道、服务器是否要关闭连接）
     * @param urlParams urlParams
     * @param body body
     * @param assertStr assert 断言
     * @param setStr 保存变量
     */
    private void webSocket(HandleDes handleDes, Api api, String options, String urlParams, String body, String assertStr, String setStr) {
        handleDes.setStepType("WebSocket 请求");
        handleDes.setDetail("" + api.getName() + " url：「" + api.getUrl() + "」" + " method：「" + api.getMethod() + "」" + " body：「" + body + "」" + " urlParams：「" + urlParams + "」" + " assert：「" + assertStr + "」" + " set：「" + setStr + "」" + " options：「" + options + "」");

        try {
            String url = api.getUrl();
            String desc = api.getName();
            String method = api.getMethod();

            JSONObject optionsJSONObject = JSONObject.parseObject(options);
            String channel = optionsJSONObject.getString("channel"); // 通道

            // api 扩展数据
            JSONObject extra = JSONObject.parseObject(api.getExtra1());
            String handleClass = extra.getString("handleClass"); // WebSocket 处理类名称
            Long sendInterval = extra.getLong("sendInterval"); // 发送间隔
            EndingType endingType = EnumUtil.valueOf(EndingType.class, extra.getIntValue("endingType")); // 接收结束条件
            boolean lose = extra.getBooleanValue("lose"); // 是否丢包

            // 组装 urlParams 参数
            List<KeyValueStore> urlParamList = new ArrayList<>();
            JSON.parseObject(urlParams).forEach((k, v) -> {
                if (v == null) {
                    urlParamList.add(new KeyValueStore(k, v));
                } else {
                    urlParamList.add(new KeyValueStore(k, v.toString()));
                }
            });

            // 组装 body 参数
            List<KeyValueStore> bodyParamList = new ArrayList<>();
            bodyParamList.add(new KeyValueStore("sendInterval", sendInterval));
            bodyParamList.add(new KeyValueStore("lose", lose));
            if (StringUtils.isNotEmpty(body)) {
                bodyParamList.add(new KeyValueStore("param", body));
            }
            bodyParamList.add(new KeyValueStore("assertStr", assertStr));
            bodyParamList.add(new KeyValueStore("setStr", setStr));
            bodyParamList.add(new KeyValueStore("options", optionsJSONObject));

            WebSocketExecuteCommand webSocketExecuteCommand = new WebSocketExecuteCommand(handleClass, this.context, logUtil, String.valueOf(logUtil.resultId), String.valueOf(logUtil.caseId), String.valueOf(logUtil.iterationCount), desc, method, url, channel, urlParamList, endingType);
            this.response = webSocketExecuteCommand.execute(bodyParamList);
        } catch (Exception e) {
            handleDes.setE(e);
        }
    }

    /**
     * 保存变量（会根据你指定的 key 从上一个请求的返回中取 value，保存到你指定的变量中）
     * @param handleDes handleDes
     * @param paras 待保存的的参数列表
     */
    public void saveParas(HandleDes handleDes, String paras) {
        handleDes.setStepType("保存变量");
        try {
            if (response.getBody() == null) {
                throw new Exception("response.body 为空，保存变量必须在任意一个有 response 请求之后使用。");
            }
            JsonHelper jsonHelper = new JsonHelper(response.getBody()); // 将请求返回的内容转成 JsonHelper
            if (jsonHelper != null) {
                JSONObject parasJsonObject = JSONObject.parseObject(paras); // 待保存的的参数列表

                StringBuffer sb = new StringBuffer(); // handle Detail 信息
                for (Map.Entry<String, Object> entry : parasJsonObject.entrySet()) {
                    String key = entry.getKey(); // 得到变量名
                    Object value = jsonHelper.get(entry.getValue().toString()); // 得到变量内容
                    if (value == null) {
                        throw new MyException("保存变量异常，未找到对应 key 的数据。 key：" + entry.getValue().toString());
                    }
                    this.context.addGlobalParas(key, value); // 将需要保存的变量存入 context 中
                    sb.append("「" + key + ": " + value + "」"); // 添加入 handle Detail 信息
                }
                handleDes.setDetail(sb.toString());
            }
        } catch (Exception e) {
            handleDes.setE(e);
        }
    }

    /**
     * 设置变量（会根据你指定的 key 和 value 保存为变量）
     * @param handleDes handleDes
     * @param paras 待设置的的参数列表
     */
    public void setParas(HandleDes handleDes, String paras) {
        handleDes.setStepType("设置变量");
        try {
            JSONObject parasJsonObject = JSONObject.parseObject(paras); // 待设置的的参数列表

            StringBuffer sb = new StringBuffer(); // handle Detail 信息
            for (Map.Entry<String, Object> entry : parasJsonObject.entrySet()) {
                String key = entry.getKey(); // 得到变量名
                Object value = entry.getValue(); // 得到变量内容
                this.context.addGlobalParas(key, value); // 将需要保存的变量存入 context 中
                sb.append("「" + key + ": " + value + "」"); // 添加入 handle Detail 信息
            }
            handleDes.setDetail(sb.toString());
        } catch (Exception e) {
            handleDes.setE(e);
        }
    }

    /**
     * 运行自定义脚本
     * @param handleDes handleDes
     * @param scriptStr 脚本内容
     * @param type 脚本类型
     */
    public void runScript(HandleDes handleDes, String scriptStr, String type) {
        handleDes.setStepType("自定义脚本");
        StringBuffer sb = new StringBuffer();
        sb.append("Script: " + scriptStr + "\n");

        try {
            switch (type.toLowerCase()) {
                case "groovy":
                    Script groovyScript = new GroovyScript();
                    this.response = groovyScript.run(this.response, this.context.getGlobalParas(), scriptStr);
                    break;
                case "python":
                    Script pythonScript = new PythonScript();
                    this.response = pythonScript.run(this.response, this.context.getGlobalParas(), scriptStr);
                    break;
                case "javascript":
                    Script javaScriptScript = new JavaScriptScript();
                    this.response = javaScriptScript.run(this.response, this.context.getGlobalParas(), scriptStr);
                    break;
                default:
                    throw new Exception("暂不支持该类型脚本，type: " + type);
            }

            sb.append("请求结果: ");
            sb.append(this.response.getBody().toString());
            handleDes.setDetail(sb.toString());
        } catch (Throwable e) {
            handleDes.setE(e);
        }
    }

    /**
     * 数据库请求
     * @param handleDes handleDes
     * @param api api
     * @param sql sql 语句
     */
    public void dataBase(HandleDes handleDes, Api api, String sql) {
        handleDes.setStepType("DataBase 请求");
        StringBuffer sb = new StringBuffer();
        sb.append(api.getName() + " sql:「" + sql + "」\n");

        try {
            String url = api.getUrl();
            String desc = api.getName();

            // api 扩展数据
            JSONObject extra = JSONObject.parseObject(api.getExtra1());
            String databaseType = extra.getString("databaseType"); // 数据库类型
            if ("mysql".equals(databaseType)) { // mysql
                String driverClass = "com.mysql.cj.jdbc.Driver"; // 数据库驱动
                String username = extra.getString("username"); // 数据库用户名
                String password = extra.getString("password"); // 数据库密码

                List<KeyValueStore> params = new ArrayList<>();
                params.add(new KeyValueStore("driverClass", driverClass));
                params.add(new KeyValueStore("url", url));
                params.add(new KeyValueStore("username", username));
                params.add(new KeyValueStore("password", password));
                params.add(new KeyValueStore("sql", sql));

                ExecuteCommand command = new SqlStepCommand( String.valueOf(logUtil.resultId), String.valueOf(logUtil.caseId), String.valueOf(logUtil.iterationCount), desc);
                this.response = command.execute(params);
            } else if ("redis".equals(databaseType)) { // redis
                String driverClass = "redis.clients.jedis.Jedis"; // 数据库驱动
                String port = extra.getString("port"); // 端口号
                String db = extra.getString("db"); // 端口号
                String password = extra.getString("password"); // 数据库密码

                List<KeyValueStore> params = new ArrayList<>();
                params.add(new KeyValueStore("driverClass", driverClass));
                params.add(new KeyValueStore("url", url));
                params.add(new KeyValueStore("port", port));
                params.add(new KeyValueStore("db", db));
                params.add(new KeyValueStore("password", password));
                params.add(new KeyValueStore("sql", sql));

                ExecuteCommand command = new RedisStepCommand( String.valueOf(logUtil.resultId), String.valueOf(logUtil.caseId), String.valueOf(logUtil.iterationCount), desc);
                this.response = command.execute(params);
            } else {
                throw new MyException("不支持的数据库类型！databaseType: " + databaseType);
            }

            sb.append("请求结果: ");
            sb.append(this.response.getBody().toString());
            handleDes.setDetail(sb.toString());
        } catch (Exception e) {
            String message = String.format("数据库步骤 错误 「sql=%s」", sql);
            logger.error(message, e);
            handleDes.setE(e);
        }
    }
}
